package com.innovation.chengzhangriji.ui.widget;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.RelativeLayout;

import com.innovation.chengzhangriji.R;

public class ExtraSwipeRefreshLayout extends RelativeLayout implements NestedScrollingParent {

    private static final String LOG_TAG = ExtraSwipeRefreshLayout.class.getSimpleName();

    private MaterialProgressDrawable mProgress;
    private CircleImageView mCircleView;

    private boolean
            mRefreshing = false,
            mNestedScrollInProgress = false,
            mOriginalOffsetCalculated = false,
            mNotify = false;

    private final DecelerateInterpolator mDecelerateInterpolator;
    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;

    private int mActivePointerId = INVALID_POINTER;

    private NestedScrollingParentHelper mParentHelper;

    private View mTarget; // the target of the gesture

    private OverScrollView mOverScrollView;

    private float mInitialDownY;

    private static final int INVALID_POINTER = -1;

    private static final float DRAG_RATE = .5f;

    private static final int DEFAULT_CIRCLE_TARGET = 64;

    // mCircleView 的大小
    private static final int CIRCLE_DIAMETER = 40;
    private int mCircleWidth, mCircleHeight;

    // 最大移动距离
    private float mTotalDragDistance = -1;

    // 总共向下的距离
    private float mTotalUnconsumed;

    // mCircleView 的位置
    private int
            mCurrentTargetOffsetTop = 0,
            mOriginalOffsetTop = 0,
            mFrom = 0;

    private static final int ANIMATE_TO_TRIGGER_DURATION = 250;

    private static final int ANIMATE_TO_START_DURATION = 1500;

    /**
     * Classes that wish to be notified when the swipe gesture correctly
     * triggers a refresh should implement this interface.
     */
    public interface OnRefreshListener {
        public void onRefresh();
    }

    // 刷新监听器
    private OnRefreshListener mListener;

    /**
     * Set the listener to be notified when a refresh is triggered via the swipe
     * gesture.
     */
    public void setOnRefreshListener(OnRefreshListener listener) {
        mListener = listener;
    }

    public ExtraSwipeRefreshLayout(Context context) {
        this(context, null);
    }

    public ExtraSwipeRefreshLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ExtraSwipeRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mParentHelper = new NestedScrollingParentHelper(this);

        // 最大移动距离
        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mTotalDragDistance = DEFAULT_CIRCLE_TARGET * metrics.density;
        mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
        mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);

        // 动画减速器
        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);

        // 添加刷新进度控件
        createProgressView();
    }

    private void createProgressView() {
        mCircleView = new CircleImageView(getContext());
        LayoutParams params = new LayoutParams(mCircleWidth, mCircleHeight);
        mCircleView.setLayoutParams(params);
        mProgress = new MaterialProgressDrawable(mCircleView, getResources(),
                BitmapFactory.decodeResource(getResources(), R.mipmap.icon_loading2));
        mCircleView.setVisibility(View.GONE);
        mCircleView.setImageDrawable(mProgress);
        addView(mCircleView, getChildCount() - 1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
        if (!mOriginalOffsetCalculated) {
            mOriginalOffsetCalculated = true;
            mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
        }
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return isEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
        mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
        // 接受时，将值置为 0
        mTotalUnconsumed = 0;
        mNestedScrollInProgress = true;
    }

    @Override
    public void onStopNestedScroll(View target) {
        mParentHelper.onStopNestedScroll(target);
        mNestedScrollInProgress = false;
        if (mTotalUnconsumed > 0) {
            finishSpinner(mTotalUnconsumed);
            mTotalUnconsumed = 0;
        }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        System.out.println("onNestedScroll:" + dxConsumed + "," + dyConsumed + "," + dxUnconsumed + "," + dyUnconsumed);
        // 向下滑动
        if (dyUnconsumed < 0) {
            mTotalUnconsumed += Math.abs(dyUnconsumed);
            moveSpinner(mTotalUnconsumed);
        }
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        System.out.println("onNestedPreScroll:" + dx + "," + dy + "," + consumed[0] + "," + consumed[1]);
        if (dy > 0 && mTotalUnconsumed > 0) {
            if (dy > mTotalUnconsumed) {
                consumed[1] = dy - (int) mTotalUnconsumed;
                mTotalUnconsumed = 0;
            } else {
                mTotalUnconsumed -= dy;
                consumed[1] = dy;

            }
            moveSpinner(mTotalUnconsumed);
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public int getNestedScrollAxes() {
        return mParentHelper.getNestedScrollAxes();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                final float initialDownY = getMotionEventY(ev, mActivePointerId);
                if (initialDownY == -1) {
                    return false;
                }
                mInitialDownY = initialDownY;
                break;

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mActivePointerId = INVALID_POINTER;
                break;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex = -1;

        // 加 canChildScrollUp() 可以限制 RecyclerView 滚到顶部才能刷新
        if (!isEnabled() || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                break;

            case MotionEvent.ACTION_MOVE: {
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }

                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float overscrollTop = (y - mInitialDownY) * DRAG_RATE;
                if (overscrollTop > 0) {
                    moveSpinner(overscrollTop);
                } else {
                    return false;
                }
                break;
            }
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                pointerIndex = MotionEventCompat.getActionIndex(ev);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
                    return false;
                }
                mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP: {
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
                    return false;
                }

                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float overscrollTop = (y - mInitialDownY) * DRAG_RATE;
                finishSpinner(overscrollTop);
                mActivePointerId = INVALID_POINTER;
                return false;
            }
            case MotionEvent.ACTION_CANCEL:
                return false;
        }
        return true;
    }

    /**
     * RecyclerView
     */
    public void setRefreshTarget(RecyclerView recyclerView) {
        mTarget = recyclerView;
    }

    /**
     * OverScrollView
     */
    public void setNestedScrollLayout(int viewId) {
        mOverScrollView = (OverScrollView) findViewById(viewId);
    }

    private void moveSpinner(float overscrollTop) {
        System.out.println("滚动了" + overscrollTop);
        float scrollDistance = overscrollTop * DRAG_RATE;
        if (mOverScrollView != null) {
            mOverScrollView.smoothScrollToPosition(0, (int) -scrollDistance);
        }
        if(!mRefreshing) {
            mCircleView.clearAnimation();
            mCircleView.setVisibility(View.VISIBLE);
            float originalDragPercent = scrollDistance / mTotalDragDistance;
            mProgress.setProgressRotation(originalDragPercent * 360);

            // 最大拉动距离为 mTotalDragDistance
            float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
//            System.out.println("dragPercent:" + dragPercent);
            int targetY = mOriginalOffsetTop + (int) ((mTotalDragDistance * dragPercent));
//            System.out.println("targetY:" + targetY);
            setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
        }
    }

    private void finishSpinner(float overscrollTop) {
//        System.out.println("停止了," + overscrollTop);
        if (mOverScrollView != null) {
            mOverScrollView.smoothScrollToPosition(0, 0);
        }
        if(!mRefreshing) {
            // 刷新控件已经到达刷新位置
            if (overscrollTop >= mTotalDragDistance - mOriginalOffsetTop) {
                setRefreshing(true, true);
            } else {
                mRefreshing = false;
                animateOffsetToStartPosition(mCurrentTargetOffsetTop);
            }
        }
    }

    /**
     * Notify the widget that refresh state has changed. Do not call this when
     * refresh is triggered by a swipe gesture.
     *
     * @param refreshing Whether or not the view should show refresh progress.
     */
    public void setRefreshing(boolean refreshing) {
        if (refreshing && mRefreshing != refreshing) {
            // scale and show
            mRefreshing = refreshing;
            mNotify = true;
            animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
            mCircleView.setVisibility(View.VISIBLE);
        } else {
            setRefreshing(refreshing, false /* notify */);
        }
    }

    // 刷新
    private void setRefreshing(boolean refreshing, final boolean notify) {
        if (mRefreshing != refreshing) {
            mNotify = notify;
            ensureTarget();
            mRefreshing = refreshing;
            if (mRefreshing) {
                animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
            } else {
                animateOffsetToStartPosition(mCurrentTargetOffsetTop);
            }
        }
    }

    // 刷新监听器
    private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            if (mRefreshing) {
                mProgress.start();
                if (mNotify) {
                    if (mListener != null) {
                        mListener.onRefresh();
                    }
                } else {
                    mProgress.stop();
                    mCurrentTargetOffsetTop = mCircleView.getTop();
                    mCircleView.setVisibility(View.GONE);
                }
            }
        }
    };

    // 回到正确的刷新位置
    private void animateOffsetToCorrectPosition(int from, Animation.AnimationListener listener) {
        mFrom = from;
        mAnimateToCorrectPosition.reset();
        mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
        mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
        if (listener != null) {
            mCircleView.setAnimationListener(listener);
        }
        mCircleView.clearAnimation();
        mCircleView.startAnimation(mAnimateToCorrectPosition);
    }

    // 将进度控件回到刷新位置，准备开始刷新进度动画
    private final Animation mAnimateToCorrectPosition = new Animation() {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
            int targetTop = 0;
            int endTarget = (int) mTotalDragDistance - Math.abs(mOriginalOffsetTop);
            targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
            int offset = targetTop - mCircleView.getTop();
            setTargetOffsetTopAndBottom(offset, false /* requires update */);
        }
    };

    // 将刷新控件回到初始位置
    private void animateOffsetToStartPosition(int from) {
        mFrom = from;
        mAnimateToStartPosition.reset();
        mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
        mCircleView.clearAnimation();
        mCircleView.startAnimation(mAnimateToStartPosition);
    }

    // 回到初始位置的动画
    private final Animation mAnimateToStartPosition = new Animation() {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
            moveToStart(interpolatedTime);
        }
    };

    // 移动位置
    private void moveToStart(float interpolatedTime) {
        int targetTop = 0;
        targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
        int offset = targetTop - mCircleView.getTop();
        setTargetOffsetTopAndBottom(offset, false /* requires update */);
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }

    private float getMotionEventY(MotionEvent ev, int activePointerId) {
        final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
        if (index < 0) {
            return -1;
        }
        return MotionEventCompat.getY(ev, index);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int circleWidth = mCircleView.getMeasuredWidth();
        int circleHeight = mCircleView.getMeasuredHeight();
        mCircleView.layout(circleWidth, mCurrentTargetOffsetTop,
                (circleWidth * 2), mCurrentTargetOffsetTop + circleHeight);
    }

    private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
        mCircleView.bringToFront();
//        System.out.println("offset:" + offset);
        mCircleView.offsetTopAndBottom(offset);
        mCurrentTargetOffsetTop = mCircleView.getTop();
//        System.out.println("mCurrentTargetOffsetTop:" + mCurrentTargetOffsetTop);
        if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
            invalidate();
        }
    }

    private void ensureTarget() {
        // Don't bother getting the parent height if the parent hasn't been laid
        // out yet.
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(mCircleView)) {
                    mTarget = child;
                    break;
                }
            }
        }
    }

    /**
     * @return Whether it is possible for the child view of this layout to
     * scroll up. Override this if the child view is a custom view.
     */
    public boolean canChildScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1);
        }
    }
}
